/*
 * Copyright (c) 2011. c.mueller
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * The Software shall be used for Good, not Evil.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package de.altitude.cimuelle.coop;

import de.altitude.cimuelle.db.DatabaseCoopData;
import de.altitude.cimuelle.db.DatabaseManager;
import de.altitude.cimuelle.enums.Plane;
import de.altitude.cimuelle.map.AltitudeMap;
import de.altitude.cimuelle.utils.StringUtils;
import de.altitudecustomcommands.Player;
import de.altitudecustomcommands.virtualServer;
import org.apache.log4j.Logger;

import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.util.*;

/**
 * Class description:
 * <p/>
 * User: cybernaut
 * Date: 06.10.11
 * Time: 23:32
 */
public class CoopGame {
    private int databaseId;

    private String port;
    private Set<String> vaporIds;
    private CoopPlayerArrayList leftTeamPlayers;
    private CoopPlayerArrayList rightTeamPlayers;
    private int leftTeam;
    private int rightTeam;
    private double duration;
    private int winningTeam;
    private AltitudeMap map;

    private int rank = -1;

    private String baseDestroyedBy;
    private CoopGameStats gameStats;

    private boolean active;

    private final static Logger LOGGER = Logger.getLogger("CoopGame");
    private boolean baseDestroyed = false;
    private boolean isChargePlanted = false;

    private int explodetCount = 0;
    private int loopyCount = 0;
    private int mirandaCount = 0;
    private int biplaneCount = 0;
    private int bomberCount = 0;

    private Timestamp start;
    private Timestamp stop;

    public CoopGame(String port, virtualServer serverByPort) {
        this.port = port;
        this.leftTeam = serverByPort.getLeftTeam();
        this.rightTeam = serverByPort.getRightTeam();
        this.map = new AltitudeMap(serverByPort.getMode(), serverByPort.getMap());

        leftTeamPlayers = new CoopPlayerArrayList();
        rightTeamPlayers = new CoopPlayerArrayList();

        vaporIds = new HashSet<String>();
        gameStats = new CoopGameStats();
        active = true;
        start = new Timestamp(Calendar.getInstance().getTime().getTime());
    }

    public void saveDatabaseInit() {
        databaseId = insertIntialDataToDatabase();
    }

    public CoopGame(int rank, int id, String port, String vapors, String leftPlayers, String rightPlayers, int leftTeam, int rightTeam, int winningTeam, String map, String mode, String baseDestroyedBy, int turretsDestroyed, int turretsDamageDone, int playerDied, int activePlayer, double duration, CoopPlayerArrayList leftCoopPlayers, CoopPlayerArrayList rightCoopPlayers, Timestamp startTime, Timestamp stopTime) {
        this.rank = rank;
        this.databaseId = id;
        this.port = port;
        this.vaporIds = new HashSet<String>();
        if (vapors != null) {
            StringTokenizer vapor = new StringTokenizer(vapors, ",");
            while (vapor.hasMoreElements()) {
                this.vaporIds.add(vapor.nextToken());
            }
        }
        this.baseDestroyedBy = baseDestroyedBy;
        baseDestroyed = baseDestroyedBy != null && !"".equals(baseDestroyedBy);
        this.leftTeamPlayers = leftCoopPlayers;
        this.leftTeam = leftTeam;
        this.rightTeamPlayers = rightCoopPlayers;
        this.rightTeam = rightTeam;
        this.winningTeam = winningTeam;
        this.map = new AltitudeMap(mode, map);
        this.gameStats = new CoopGameStats(activePlayer, playerDied, turretsDamageDone, turretsDestroyed);
        this.duration = duration;
        this.start = startTime;
        this.stop = stopTime;
    }

    private int insertIntialDataToDatabase() {
        return DatabaseCoopData.initialData(this);
    }

    public void addPlayerToTeam(Player player) {
        final int playerTeam = player.getTeam();
        if (playerTeam == leftTeam) {
            addPlayer(player, true);
        } else if (playerTeam == rightTeam) {
            addPlayer(player, false);
        } else {
            LOGGER.error("Player: " + player.getName() + " is not on a valid team (" + player.getTeam() + ").");
            LOGGER.error("Map: leftTeam: " + leftTeam + " | rightTeam: " + rightTeam);
        }
    }

    private void addPlayer(Player player, boolean left) {
        if (left) {
            leftTeamPlayers.add(new CoopPlayer(player));
        } else {
            rightTeamPlayers.add(new CoopPlayer(player));
        }
        vaporIds.add(player.getVapor());
        gameStats.setActivePlayer(gameStats.getActivePlayer() + 1);
    }

    public boolean isActive() {
        return active;
    }

    public String getPort() {
        return port;
    }

    public boolean isPlayerOnTeam(Player player) {
        return vaporIds.contains(player.getVapor());
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    public void setWinningTeam(int winningTeam) {
        this.winningTeam = winningTeam;
    }

    public void saveData() {
        if (databaseId > -1) {
            // TODO Second save needed ?
            DatabaseCoopData.updateData(this);
            LOGGER.info(port + ":Saving points for coop Players.");
            DatabaseManager.getInstance().addCoopPlayerPoints(port, leftTeamPlayers, this);
        }
    }

    public void setCoopRank() {
        if (databaseId > -1) {
            DatabaseCoopData.updateData(this);
            LOGGER.debug(port + ":To database: " + this);
            LOGGER.debug(port + ":Getting Rank to finished game.");
            rank = DatabaseCoopData.getRankByPortMapId(port, map.getMap(), databaseId);
            LOGGER.debug(port + ":Rank " + rank + " set.");
        }
    }

    public CoopGameStats getGameStats() {
        return gameStats;
    }

    public void setBaseDestroyedBy(Player baseDestroyedBy) {
        if (baseDestroyedBy != null) {
            this.baseDestroyedBy = baseDestroyedBy.getVapor();
            if (baseDestroyedBy.getTeam() == leftTeam) {
                leftTeamPlayers.getPlayer(baseDestroyedBy).setDestroyedBase(true);
            } else if (baseDestroyedBy.getTeam() == rightTeam) {
                rightTeamPlayers.getPlayer(baseDestroyedBy).setDestroyedBase(true);
            } else {
                LOGGER.error("Team was not found." + baseDestroyedBy);
            }
        } else {
            LOGGER.error("Null at " + baseDestroyedBy + this);
        }
    }

    public String showGameData() {
        StringBuilder sb = new StringBuilder();
        DecimalFormat df6 = new DecimalFormat("000000");
        DecimalFormat df4 = new DecimalFormat("0000");
        DecimalFormat df3 = new DecimalFormat("000");
        DecimalFormat df2 = new DecimalFormat("00");


        sb.append("CoopGame id:").append(df6.format(databaseId)).append("\n");
        sb.append("Duration: ").append(df3.format(duration)).append("seconds | ");
        sb.append("Base has ");
        if (!baseDestroyed) {
            sb.append("not ");
        }
        sb.append("been destroyed.\n");
        sb.append(df2.format(gameStats.getActivePlayer())).append(" players alive and ").append(df2.format(gameStats.getPlayerDied())).append(" died\n");
        sb.append("--------------------Player statistics----------------------\n");
        int[] len = new int[]{5, 4, 7, 4, 15};
        sb.append(StringUtils.fillTextFormat("Points", len[0], "."));
        sb.append(".|.");
        sb.append(StringUtils.fillTextFormat("kills", len[1], "."));
        sb.append(".|.");
        sb.append(StringUtils.fillTextFormat("finished", len[2], "."));
        sb.append(".|.");
        sb.append(StringUtils.fillTextFormat("alive", len[3], "."));
        sb.append(".|.");
        sb.append(StringUtils.fillTextFormat("name", len[4], "."));
        sb.append("\n");
        TreeMap<Integer, ArrayList<Integer>> pl = new TreeMap<Integer, ArrayList<Integer>>();
        if (leftTeamPlayers != null) {
            for (int i = 0; i < leftTeamPlayers.size(); i++) {
                ArrayList<Integer> list;
                if (pl.containsKey(leftTeamPlayers.get(i).getCoopPoints())) {
                    list = pl.get(leftTeamPlayers.get(i).getCoopPoints());
                } else {
                    list = new ArrayList<Integer>();
                }
                list.add(i);
                pl.put(leftTeamPlayers.get(i).getCoopPoints(), list);
            }
            CoopPlayer cp;

            for (Integer next : pl.descendingKeySet()) {
                for (int playerId : pl.get(next)) {
                    cp = leftTeamPlayers.get(playerId);
                    String playerName = cp.getName();
                    int maxLength = 20;
                    if (playerName.length() > maxLength) {
                        playerName = playerName.substring(0, (maxLength - 3)) + "...";
                    }
                    //sb.append(" ");
                    sb.append(StringUtils.fillTextFormat(df4.format(cp.getCoopPoints()), len[0], "."));
                    sb.append(".|.");
                    sb.append(StringUtils.fillTextFormat(df3.format(cp.getTurretKills()), len[1], "."));
                    sb.append(".|.");
                    if (cp.isDestroyedBase()) {
                        sb.append(StringUtils.fillTextFormat("X", len[2], "."));
                    } else {
                        sb.append(StringUtils.fillTextFormat("_", len[2], "."));
                    }
                    sb.append(".|.");
                    if (cp.isAlive()) {
                        sb.append(StringUtils.fillTextFormat("X", len[3], "."));
                    } else {
                        sb.append(StringUtils.fillTextFormat("_", len[3], "."));
                    }
                    sb.append(".|.");
                    sb.append(StringUtils.fillTextFormat(playerName, len[4], "."));
                    sb.append("\n");
                }
            }
        }
        return sb.toString();
    }

    @Override
    public String toString() {
        return "CoopGame{" +
                "databaseId=" + databaseId +
                ", port='" + port + '\'' +
                ", vaporIds=" + vaporIds +
                ", leftTeamPlayers=" + leftTeamPlayers +
                ", rightTeamPlayers=" + rightTeamPlayers +
                ", leftTeam=" + leftTeam +
                ", rightTeam=" + rightTeam +
                ", duration=" + duration +
                ", winningTeam=" + winningTeam +
                ", map=" + map +
                ", rank=" + rank +
                ", baseDestroyedBy='" + baseDestroyedBy + '\'' +
                ", gameStats=" + gameStats +
                ", active=" + active +
                ", baseDestroyed=" + baseDestroyed +
                ", explodetCount=" + explodetCount +
                ", loopyCount=" + loopyCount +
                ", mirandaCount=" + mirandaCount +
                ", biplaneCount=" + biplaneCount +
                ", bomberCount=" + bomberCount +
                ", start=" + start +
                ", stop=" + stop +
                '}';
    }

    public int getLeftTeam() {
        return leftTeam;
    }

    public int getRightTeam() {
        return rightTeam;
    }

    public AltitudeMap getMap() {
        return map;
    }

    public String getVaporIds() {
        return buildPlayerList(vaporIds);
    }

    private String buildPlayerList(Set<String> vaporIds) {
        StringBuilder sb = new StringBuilder();
        for (String vapor : vaporIds) {
            sb.append(vapor).append(",");
        }
        if (sb.toString().length() > 0) {
            return sb.toString().substring(0, (sb.toString().length() - 1));
        } else {
            return "";
        }
    }

    public int getDatabaseId() {
        return databaseId;
    }

    public String getLeftTeamPlayers() {
        return buildPlayerList(leftTeamPlayers);
    }

    public String getRightTeamPlayers() {
        return buildPlayerList(rightTeamPlayers);
    }

    private String buildPlayerList(List<CoopPlayer> playerList) {
        StringBuilder sb = new StringBuilder();
        for (CoopPlayer player : playerList) {
            sb.append(player.getVapor()).append(",");
        }
        if (sb.length() > 0) {
            return sb.toString().substring(0, (sb.toString().length() - 1));
        }
        return "";
    }

    public double getDuration() {
        return duration;
    }

    public int getWinningTeam() {
        return winningTeam;
    }

    public String getBaseDestroyedBy() {
        return baseDestroyedBy;
    }

    public void setDuration(double duration) {
        this.duration = duration;
    }

    public List<CoopPlayer> getLeftTeamPlayersAsList() {
        return leftTeamPlayers;
    }

    public List<CoopPlayer> getRightTeamPlayersAsList() {
        return rightTeamPlayers;
    }

    public int getRank() {
        return rank;
    }

    public void setPlayerAlive(Player victim, boolean alive) {
        CoopPlayer coopPlayer = getCoopPlayer(victim);
        if (coopPlayer != null) {
            coopPlayer.setAlive(alive);
        } else {
            LOGGER.error("Player not found." + victim + " retrying..");
        }
    }

    public boolean isBaseDestroyed() {
        return baseDestroyed;
    }

    public CoopPlayer getCoopPlayer(Player player) {
        CoopPlayer coopPlayer = null;
        if (player != null) {
            coopPlayer = leftTeamPlayers.getPlayer(player);
            if (coopPlayer == null) {
                coopPlayer = rightTeamPlayers.getPlayer(player);
            }
            // workaround to get player by non-unique id
            if (coopPlayer == null) {
                coopPlayer = leftTeamPlayers.getPlayerByPlayerId(player.getPlayerNumber());
                if (coopPlayer == null) {
                    coopPlayer = rightTeamPlayers.getPlayerByPlayerId(player.getPlayerNumber());
                }
            }
        } else {
            LOGGER.error("Team not found." + player);
        }
        return coopPlayer;
    }

    public CoopPlayer getCoopPlayer(String vapor) {
        Player player = new Player("", vapor, "", "", -1);
        boolean teamFound = false;
        for (CoopPlayer cPlayer : leftTeamPlayers) {
            if (vapor.equals(cPlayer.getVapor())) {
                player.setTeam(leftTeam);
                teamFound = true;
                break;
            }
        }
        if (!teamFound) {
            player.setTeam(rightTeam);
        }
        return getCoopPlayer(player);
    }

    public int getExplodetCount() {
        return explodetCount;
    }

    public int getLoopyCount() {
        return loopyCount;
    }

    public int getMirandaCount() {
        return mirandaCount;
    }

    public int getBiplaneCount() {
        return biplaneCount;
    }

    public int getBomberCount() {
        return bomberCount;
    }

    public void delete() {
        synchronized (this) {
            CoopDataHandler.deleteCoopGame(this);
            databaseId = -1;
        }
    }

    public void addPlane(Plane plane) {
        switch (plane) {
            case BOMBER:
                ++bomberCount;
                break;
            case LOOPY:
                ++loopyCount;
                break;
            case BIPLANE:
                ++biplaneCount;
                break;
            case EXPLODET:
                ++explodetCount;
                break;
            case MIRANDA:
                ++mirandaCount;
                break;
            default:
                LOGGER.error("Plane " + plane + " not known.");
        }
    }

    public void setBaseDestroyed(boolean baseDestroyed) {
        this.baseDestroyed = baseDestroyed;
    }

    public static String getShortInfoHeader() {
        return "rank,time | active | died | date";
    }

    public String getShortInfo() {
        StringBuilder sb = new StringBuilder();
        DecimalFormat decimalFormat = new DecimalFormat("00");
        DecimalFormat decimalFormat4 = new DecimalFormat("0000");

        sb.append(" #").append(decimalFormat.format(rank))
                .append(", ").append(decimalFormat4.format(duration)).append("s")
                .append(" |    ").append(decimalFormat.format(gameStats.getActivePlayer()))
                .append(" |   ").append(decimalFormat.format(gameStats.getPlayerDied()))
                .append(" | ");
        if (start != null) {
            sb.append(new Date(start.getTime()));
        } else {
            sb.append("n.n.");
        }

        return sb.toString();
    }

    public Timestamp getStart() {
        return start;
    }

    public Timestamp getStop() {
        return stop;
    }

    public void setStop(Timestamp stop) {
        this.stop = stop;
    }

    int getCurrentGameTimeInSec() {
        return (int) ((Calendar.getInstance().getTime().getTime() - start.getTime()) / 1000);
    }

    public void setPlayerAlive(Player victim, boolean alive, CoopGame coopGame) {
        CoopPlayer coopPlayer = coopGame.getCoopPlayer(victim);
        if (coopPlayer != null) {
            coopPlayer.setDiedAfterSeconds(coopGame.getCurrentGameTimeInSec());
            setPlayerAlive(victim, alive);
            if ("0".equals(victim.getVapor())) {
                coopPlayer.setLeftBeforeDie(true);
            }
        } else {
            LOGGER.error("Player could not be determined. victim ID: " + victim + " alive: " + alive + " in game " + coopGame.getDatabaseId());
        }
    }

    public boolean isChargePlanted() {
        return isChargePlanted;
    }

    public void setChargePlanted(boolean isChargePlanted) {
        this.isChargePlanted = isChargePlanted;
    }

    public void removePointsFromPlayers() {
        for (CoopPlayer cp : leftTeamPlayers) {
            LOGGER.info("Removing " + cp.getCoopPoints() + " coopPoints from " + cp.getName() + ".");
            DatabaseManager.getInstance().increaseCoopPoints(port, cp.getVapor(), cp.getCoopPoints() * -1);
        }
    }
}
